/****

    activequant - activestocks.eu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

	
	contact  : contact@activestocks.eu
    homepage : http://www.activestocks.eu

 ****/
package org.activequant.workflow.logic.operations;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

/**
 * An abstract class. Implements the IOperation interface.
 * <br>
 *       <b>History:</b><br>
 *       - [09.03.2007] Created (Erik Nijkamp)<br>
 * 
 * @author Erik Nijkamp
 */
public abstract class OperationBase implements IOperation {

	/**
	 * Lists all super types (all implemented interfaces and inherited classes).<br/>
	 * Returns a Class&lt;?&gt;[] holding all subclasses and interfaces implemented by the given clazz(Class&lt;?&gt;) including itself.
	 * @param clazz
	 *            the class whose supertypes to analyse.
	 * @return a set of the <code>Class</code>es of all interfaces and
	 *         superclasses this class implements or inherits from.
	 */
	public Class<?>[] getAllClasses(Class<?> clazz) {
		List<Class<?>> superclasses = new ArrayList<Class<?>>();
		Class<?> main = clazz;

		while (main != null) {
			superclasses.add(main);
			for (Class<?> c : main.getInterfaces()) {
				superclasses.add(c);
			}
			main = main.getSuperclass();
		}
		return superclasses.toArray(new Class<?>[] {});
	}
	/**
	 * returns a Method[] holding all methods declared(and inherited) in the given clazz(Class&lt;?&gt;) who are annotated with the given annotation(Class&lt;? extends Annotation&gt;) interface
	 * @param clazz
	 * @param annotation
	 * @return
	 */
	protected Method[] getMethodsByAnnotation(Class<?> clazz, Class<? extends Annotation> annotation) {
		Vector<Method> methods = new Vector<Method>();
		// concrete class
		for (Method method : clazz.getMethods()) {
			if (method.isAnnotationPresent(annotation)) {
				methods.add(method);
			}
		}
		// interface
		for (Class<?> inter : getAllClasses(clazz)) {
			for (Method method : inter.getMethods()) {
				if (method.isAnnotationPresent(annotation)) {
					methods.add(method);
				}
			}
		}
		if (methods.isEmpty())
			throw new IllegalArgumentException("Cannot find a method tagged with @" + annotation.getSimpleName() + " for target object '" + clazz.getName() + "'.");
		return methods.toArray(new Method[] {});
	}
	/**
	 * returns a Method[] holding all methods with the given name(String) declared in the Class of the given target(Object)
	 * @param target
	 * @param name
	 * @return
	 */
	protected Method[] getMethodsByName(Object target, String name) {
		Vector<Method> methods = new Vector<Method>();
		for (Method method : target.getClass().getMethods()) {
			if (method.getName().equals(name)) {
				methods.add(method);
			}
		}
		if (methods.isEmpty())
			throw new IllegalArgumentException("Cannot find method '" + name + "' for target object '" + target.getClass().getName() + "'.");
		return methods.toArray(new Method[] {});
	}
	/**
	 * returns the first Method from the given methods(Method[]) whose return type is assignable from the given returnType(Class&lt;?&gt;)
	 * @param methods
	 * @param returnType
	 * @return
	 */
	protected Method getMethodByReturnType(Method[] methods, Class<?> returnType) {
		for (Method method : methods) {
			if (method.getReturnType().isAssignableFrom(returnType))
				return method;
		}
		throw new IllegalArgumentException("Cannot find method for signature.");
	}
	/**
	 * returns the first Method from the given methods(Method[]) whose signature matches the given params(Object[])
	 * @param methods
	 * @param params
	 * @return
	 */
	protected Method getMethodBySignature(Method[] methods, Object[] params) {
		for (Method method : methods) {
			if (isSignatureIdentical(method.getParameterTypes(), params))
				return method;
		}
		throw new IllegalArgumentException("Cannot find method for signature.");
	}
	/**
	 * returns whether the Class each Object in the given second(Object[]) can be assigned from the Class at the same index in the given first(Class&lt;?&gt;[])
	 * @param first
	 * @param second
	 * @return
	 */
	private boolean isSignatureIdentical(Class<?>[] first, Object[] second) {
		Class<?>[] types = new Class<?>[second.length];
		for (int i = 0; i < types.length; i++)
			types[i] = second[i].getClass();
		return isSignatureIdentical(first, types);
	}
	/**
	 * returns whether each element in the given second(Class&lt;?&gt;[]) can be assigned from the element at the same index in the given first(Class&lt;?&gt;[])
	 * @param first
	 * @param second
	 * @return
	 */
	private boolean isSignatureIdentical(Class<?>[] first, Class<?>[] second) {
		assert (first.length == second.length) : "first.length != second.length";
		for (int i = 0; i < first.length; i++) {
			// check for possible up-cast or direct assignment
			if (!first[i].isAssignableFrom(second[i]))
				return false;
		}
		return true;
	}

}
